/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "UpdateMask.h"
#include "Path.h"
#include "WaypointMovementGenerator.h"
#include "DestinationHolderImp.h"

void WorldSession::HandleTaxiNodeStatusQueryOpcode(WorldPacket & recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_TAXINODE_STATUS_QUERY");

	uint64 guid;

	recv_data >> guid;
	SendTaxiStatus(guid);
}

void WorldSession::SendTaxiStatus(uint64 guid) {
	// cheating checks
	Creature *unit = GetPlayer()->GetMap()->GetCreature(guid);
	if (!unit) {
		sLog->outDebug(LOG_FILTER_NETWORKIO,
				"WorldSession::SendTaxiStatus - Unit (GUID: %u) not found.",
				uint32(GUID_LOPART(guid)));
		return;
	}

	uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(),
			unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(),
			GetPlayer()->GetTeam());

	// not found nearest
	if (curloc == 0)
		return;

	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: current location %u ", curloc);

	WorldPacket data(SMSG_TAXINODE_STATUS, 9);
	data << guid;
	data << uint8(GetPlayer()->m_taxi.IsTaximaskNodeKnown(curloc) ? 1 : 0);
	SendPacket(&data);
	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Sent SMSG_TAXINODE_STATUS");
}

void WorldSession::HandleTaxiQueryAvailableNodes(WorldPacket & recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_TAXIQUERYAVAILABLENODES");

	uint64 guid;
	recv_data >> guid;

	// cheating checks
	Creature *unit = GetPlayer()->GetNPCIfCanInteractWith(guid,
			UNIT_NPC_FLAG_FLIGHTMASTER);
	if (!unit) {
		sLog->outDebug(
				LOG_FILTER_NETWORKIO,
				"WORLD: HandleTaxiQueryAvailableNodes - Unit (GUID: %u) not found or you can't interact with him.",
				uint32(GUID_LOPART(guid)));
		return;
	}

	// remove fake death
	if (GetPlayer()->HasUnitState(UNIT_STAT_DIED))
		GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);

	// unknown taxi node case
	if (SendLearnNewTaxiNode(unit))
		SendTaxiMenu(unit);

	// known taxi node case
	SendTaxiMenu(unit);
}

void WorldSession::SendTaxiMenu(Creature* unit) {
	// find current node
	uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(),
			unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(),
			GetPlayer()->GetTeam());

	if (curloc == 0)
		return;

	bool lastTaxiCheaterState = GetPlayer()->isTaxiCheater();
	if (unit->GetEntry() == 29480)
		GetPlayer()->SetTaxiCheater(true); // Grimwing in Ebon Hold, special case. NOTE: Not perfect, Zul'Aman should not be included according to WoWhead, and I think taxicheat includes it.

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: CMSG_TAXINODE_STATUS_QUERY %u ", curloc);

	WorldPacket data(SMSG_SHOWTAXINODES, (4 + 8 + 4 + 8 * 4), true);
	data << uint32(1);
	data << uint64(unit->GetGUID());
	data << uint32(curloc);
	data << uint8((sTaxiNodesStore.GetNumRows() >> 6) + 1); // This is 11 as of 4.0.6 13623 (count of following uint64's)
	GetPlayer()->m_taxi.AppendTaximaskTo(data, GetPlayer()->isTaxiCheater());
	SendPacket(&data);

	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Sent SMSG_SHOWTAXINODES");

	GetPlayer()->SetTaxiCheater(lastTaxiCheaterState);
}

void WorldSession::SendDoFlight(uint32 mountDisplayId, uint32 path,
		uint32 pathNode) {
	// remove fake death
	if (GetPlayer()->HasUnitState(UNIT_STAT_DIED))
		GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);

	while (GetPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType()
			== FLIGHT_MOTION_TYPE)
		GetPlayer()->GetMotionMaster()->MovementExpired(false);

	if (mountDisplayId)
		GetPlayer()->Mount(mountDisplayId);

	GetPlayer()->GetMotionMaster()->MoveTaxiFlight(path, pathNode);
}

bool WorldSession::SendLearnNewTaxiNode(Creature* unit) {
	// find current node
	uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(),
			unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(),
			GetPlayer()->GetTeam());

	if (curloc == 0)
		return true; // `true` send to avoid WorldSession::SendTaxiMenu call with one more curlock seartch with same false result.

	if (GetPlayer()->m_taxi.SetTaximaskNode(curloc)) {
		WorldPacket msg(SMSG_NEW_TAXI_PATH, 0);
		SendPacket(&msg);

		WorldPacket update(SMSG_TAXINODE_STATUS, 9);
		update << uint64(unit->GetGUID());
		update << uint8(1);
		SendPacket(&update);

		return true;
	} else
		return false;
}

void WorldSession::SendDiscoverNewTaxiNode(uint32 nodeid) {
	if (GetPlayer()->m_taxi.SetTaximaskNode(nodeid)) {
		WorldPacket msg(SMSG_NEW_TAXI_PATH, 0);
		SendPacket(&msg);
	}
}

void WorldSession::HandleActivateTaxiExpressOpcode(WorldPacket & recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_ACTIVATETAXIEXPRESS");

	uint64 guid;
	uint32 node_count;

	recv_data >> guid >> node_count;

	Creature *npc = GetPlayer()->GetNPCIfCanInteractWith(guid,
			UNIT_NPC_FLAG_FLIGHTMASTER);
	if (!npc) {
		sLog->outDebug(
				LOG_FILTER_NETWORKIO,
				"WORLD: HandleActivateTaxiExpressOpcode - Unit (GUID: %u) not found or you can't interact with it.",
				uint32(GUID_LOPART(guid)));
		return;
	}
	std::vector<uint32> nodes;

	for (uint32 i = 0; i < node_count; ++i) {
		uint32 node;
		recv_data >> node;
		nodes.push_back(node);
	}

	if (nodes.empty())
		return;

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_ACTIVATETAXIEXPRESS from %d to %d",
			nodes.front(), nodes.back());

	GetPlayer()->ActivateTaxiPathTo(nodes, npc);
}

void WorldSession::HandleMoveSplineDoneOpcode(WorldPacket& recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_MOVE_SPLINE_DONE");

	uint64 guid; // used only for proper packet read
	recv_data.readPackGUID(guid);

	MovementInfo movementInfo; // used only for proper packet read
	ReadMovementInfo(recv_data, &movementInfo);

	recv_data.read_skip<uint32>(); // unk

	// in taxi flight packet received in 2 case:
	// 1) end taxi path in far (multi-node) flight
	// 2) switch from one map to other in case multim-map taxi path
	// we need process only (1)

	uint32 curDest = GetPlayer()->m_taxi.GetTaxiDestination();
	if (!curDest)
		return;

	TaxiNodesEntry const* curDestNode = sTaxiNodesStore.LookupEntry(curDest);

	// far teleport case
	if (curDestNode && curDestNode->map_id != GetPlayer()->GetMapId()) {
		if (GetPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType()
				== FLIGHT_MOTION_TYPE) {
			// short preparations to continue flight
			FlightPathMovementGenerator* flight =
					(FlightPathMovementGenerator*) (GetPlayer()->GetMotionMaster()->top());

			flight->SetCurrentNodeAfterTeleport();
			TaxiPathNodeEntry const& node =
					flight->GetPath()[flight->GetCurrentNode()];
			flight->SkipCurrentNode();

			GetPlayer()->TeleportTo(curDestNode->map_id, node.x, node.y, node.z,
					GetPlayer()->GetOrientation());
		}
		return;
	}

	uint32 destinationnode = GetPlayer()->m_taxi.NextTaxiDestination();
	if (destinationnode > 0) // if more destinations to go
			{
		// current source node for next destination
		uint32 sourcenode = GetPlayer()->m_taxi.GetTaxiSource();

		// Add to taximask middle hubs in taxicheat mode (to prevent having player with disabled taxicheat and not having back flight path)
		if (GetPlayer()->isTaxiCheater()) {
			if (GetPlayer()->m_taxi.SetTaximaskNode(sourcenode)) {
				WorldPacket data(SMSG_NEW_TAXI_PATH, 0);
				_player->GetSession()->SendPacket(&data);
			}
		}

		sLog
				->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Taxi has to go from %u to %u", sourcenode, destinationnode);

				uint32 mountDisplayId = sObjectMgr->GetTaxiMountDisplayId(sourcenode, GetPlayer()->GetTeam());

				uint32 path, cost
;
		sObjectMgr->GetTaxiPath(sourcenode, destinationnode, path, cost);

		if (path && mountDisplayId)
			SendDoFlight(mountDisplayId, path, 1); // skip start fly node
		else
			GetPlayer()->m_taxi.ClearTaxiDestinations(); // clear problematic path and next
		return;
	} else
		GetPlayer()->m_taxi.ClearTaxiDestinations(); // not destinations, clear source node

	GetPlayer()->CleanupAfterTaxiFlight();
	GetPlayer()->SetFallInformation(0, GetPlayer()->GetPositionZ());
	if (GetPlayer()->pvpInfo.inHostileArea)
		GetPlayer()->CastSpell(GetPlayer(), 2479, true);
}

void WorldSession::HandleActivateTaxiOpcode(WorldPacket & recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Received CMSG_ACTIVATETAXI");

	uint64 guid;
	std::vector<uint32> nodes;
	nodes.resize(2);

	recv_data >> guid >> nodes[0] >> nodes[1];
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Received CMSG_ACTIVATETAXI from %d to %d", nodes[0],
			nodes[1]);
	Creature *npc = GetPlayer()->GetNPCIfCanInteractWith(guid,
			UNIT_NPC_FLAG_FLIGHTMASTER);
	if (!npc) {
		sLog->outDebug(
				LOG_FILTER_NETWORKIO,
				"WORLD: HandleActivateTaxiOpcode - Unit (GUID: %u) not found or you can't interact with it.",
				uint32(GUID_LOPART(guid)));
		return;
	}

	GetPlayer()->ActivateTaxiPathTo(nodes, npc);
}
